package cn.cosmosx.base.util.check;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * 统一社会信用代码校验工具
 *
 * @author gengzhy
 * @date 2022-9-22
 */
public final class CreditCodeCheckUtil {
    /**
     * 信用代码字符集
     */
    private final static String[] CODE_SOURCE = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
                                                  "A", "B", "C", "D", "E", "F", "G", "H", "J", "K",
                                                  "L", "M", "N", "P", "Q", "R", "T", "U", "W", "X", "Y" };

    /**
     * 信用代码字符集对应的数值
     */
    private final static String[] CODE_SOURCE_TARGET_VALUE = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
                                                               "10", "11", "12", "13", "14", "15", "16", "17",
                                                               "18", "19", "20", "21", "22", "23", "24", "25",
                                                               "26", "27", "28", "29", "30" };

    /**
     * 第1~17位信用代码对应的加权因子
     */
    private final static int[] FACTOR = { 1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28 };

    /**
     * 信用代码正则
     * 1、统一代码由十八位的数字或大写英文字母（不适用I、O、Z、S、V）组成，第18位为校验位。
     * 2、第1位为数字或大写英文字母，登记管理部门代码
     * 3、第2位为数字或大写英文字母，机构类别代码
     * 4、第3到8位共6位全为数字登记管理机关行政区划码
     * 5、第9-17位共9位为数字或大写英文字母组织机构代码
     * 6、第18为为数字或者大写的字母
     */
    private final static Pattern REGEX_CREDIT_CODE = Pattern.compile("^([0-9ABCDEFGHJKLMNPQRTUWXY]{2})([0-9]{6})([0-9ABCDEFGHJKLMNPQRTUWXY]{9})([0-9ABCDEFGHJKLMNPQRTUWXY])$");

    /**
     * 信用代码字符集与其对应的数值映射
     */
    private final static Map<String, String> REFER = new HashMap<>();
    static {
        for (int i = 0, len = CODE_SOURCE.length; i < len; i++) {
            REFER.put(CODE_SOURCE[i], CODE_SOURCE_TARGET_VALUE[i]);
        }
    }

    /**
     * 统一社会信用代码校验<br>
     *
     * <pre>
     * 标准：GB 32100-2015
     * 校验规则：
     *  1、统一代码由十八位的数字或大写英文字母（不适用I、O、Z、S、V）组成，第18位为校验位。
     *  2、第1位为数字或大写英文字母，登记管理部门代码
     *  3、第2位为数字或大写英文字母，机构类别代码
     *  4、第3到8位共6位全为数字登记管理机关行政区划码
     *  5、第9-17位共9位为数字或大写英文字母组织机构代码
     *  6、第18为为数字或者大写的字母
     *  7、前17位经过计算与第18为比较
     * </pre>
     *
     * @param creditCode 统一社会信用代码
     * @return boolean True-合法；False-不合法
     */
    public static boolean verify(String creditCode) {
        // 统一代码由十八位的数字或大写英文字母（不适用I、O、Z、S、V）组成，第18位为校验位。
        // 第1位为数字或大写英文字母，登记管理部门代码
        // 第2位为数字或大写英文字母，机构类别代码
        // 第3到8位共6位全为数字登记管理机关行政区划码
        // 第9-17位共9位为数字或大写英文字母组织机构代码
        // 第18为为数字或者大写的字母
        if (!REGEX_CREDIT_CODE.matcher(creditCode).matches()) {
            return false;
        }

        // 前17为信用代码数组，经过计算得出的结果与最后一位比较
        String[] codes = creditCode.substring(0, creditCode.length() - 1).split("");
        int sum = 0;
        for (int i = 0; i < codes.length; i++) {
            String code = REFER.get(codes[i]);
            if (code == null) {
                return false;
            }
            sum += FACTOR[i] * Integer.parseInt(code);
        }
        int pos = 31 - sum % 31;
        String result = (pos == 31) ? "0" : CODE_SOURCE[pos];
        String lastCode = creditCode.substring(creditCode.length() - 1);
        return lastCode.equals(result);
    }
}
